package org.sakaiproject.basiclti.util; import java.security.AlgorithmParameters; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Random; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; /** * This is a simple class that does AES encryption of a string. * It should never be used for very much data as it's just aimed at encrypting * passwords stored in the database so you need the secret key to * decrypt them. If just hex encodes the data which balloons it a little. * <p> * It doesn't pull in any external dependenies (commons-codec or http://www.jasypt.org/) * as Basic LTI tries to keep them to a minimum. * * @author buckett * */ public class SimpleEncryption { public static final String CIPHER = "AES/CBC/PKCS5Padding"; public static String encrypt(String key, String source) { if (source == null) { return null; } byte[] salt = new byte[8]; new Random().nextBytes(salt); char[] password = key.toCharArray(); try { SecretKey secret = generateSecret(password, salt); /* Encrypt the message. */ Cipher cipher = Cipher.getInstance(CIPHER); cipher.init(Cipher.ENCRYPT_MODE, secret); AlgorithmParameters params = cipher.getParameters(); byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] ciphertext = cipher.doFinal(source.getBytes("UTF-8")); // Pack the byte arrays into a string hex encoded. StringBuffer out = new StringBuffer(); out.append(ShaUtil.byteToHex(salt)); out.append(":"); out.append(ShaUtil.byteToHex(iv)); out.append(":"); out.append(ShaUtil.byteToHex(ciphertext)); out.append(":"); out.append(CIPHER); return out.toString(); } catch (Exception e) { throw new RuntimeException(e); } } public static String decrypt(String key, String encrypted) { if (encrypted == null) { return null; } char[] password = key.toCharArray(); String parts[] = encrypted.split(":"); if ( parts.length == 4 && CIPHER.equals(parts[3]) ) { // Things are where they should be. } else { throw new RuntimeException("Corrupt encrypted source. Can't split source."); } byte[] salt = ShaUtil.hexToByte(parts[0]); byte[] iv = ShaUtil.hexToByte(parts[1]); byte[] ciphertext = ShaUtil.hexToByte(parts[2]); try { SecretKey secret = generateSecret(password, salt); Cipher cipher = Cipher.getInstance(CIPHER); cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8"); return plaintext; } catch (Exception e) { throw new RuntimeException(e); } } private static SecretKey generateSecret(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); // 128 bit keys don't cause problems with the export restrictions in the JVM // 258 bit keys only work when the JCE unlimited strength policy is installed. KeySpec spec = new PBEKeySpec(password, salt, 8, 128); SecretKey tmp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); return secret; } }